package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuAttrValue;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.feign.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.ml.job.results.Bucket;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 搜索管理
 */
@Service
public class ListServiceImpl implements ListService {


    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;

    //@Autowired
    //private ElasticsearchRestTemplate elasticsearchRestTemplate;//搜索

    //上架索引
    @Override
    public void upperGoods(Long skuId) {
        Goods goods = new Goods();
        //1:根据 skuId 查询SkuInfo对象
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuInfo.getId());
        goods.setTitle(skuInfo.getSkuName());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        goods.setCreateTime(new Date());
        //////////////////////////////////////////////////////
        //2:根据品牌ID 查询品牌对象
        BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
        goods.setTmId(trademark.getId());
        goods.setTmName(trademark.getTmName());
        goods.setTmLogoUrl(trademark.getLogoUrl());
        //////////////////////////////////////////////////////
        //3:根据三级分类的Id 查询分类视图对象
        BaseCategoryView categoryView = productFeignClient.
                getCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(categoryView.getCategory1Id());
        goods.setCategory2Id(categoryView.getCategory2Id());
        goods.setCategory3Id(categoryView.getCategory3Id());
        goods.setCategory1Name(categoryView.getCategory1Name());
        goods.setCategory2Name(categoryView.getCategory2Name());
        goods.setCategory3Name(categoryView.getCategory3Name());
        //4:根据skuId    一条Sql语句
        // 查询 关联的平台属性及属性值集合
        // 并根据平台属性ID查询名称
        // 根据平台属性值ID查询名称
        List<SkuAttrValue> baseAttrInfoAndValueList =
                productFeignClient.getBaseAttrInfoAndValue(skuId);

        List<SearchAttr> searchAttrList = baseAttrInfoAndValueList.stream().map((skuAttrValue) -> {
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(searchAttrList);
        //保存
        goodsDao.save(goods);
    }

    //更新ES的热度
    @Override
    public void incrHotScore(Long skuId) {
        //1: Redis + ES
        // 值类型
        // Hash类型
        //list
        //set
        //zset
        // 张三   数学 100  语文 99  化学 88
        // 商品热度   skuId 9   skuId 11  skuId 30
        //参数1： 商品热度  zset 容器的名称
        //参数2： 商品ID
        //参数3： 增加的分数
        //返回值 ： Double hotScore 为当前总分数
        Double hotScore = redisTemplate.opsForZSet().
                incrementScore("hotScore", skuId, 1);
        //2: //逢10
        if (hotScore % 10 == 0) {
            //更新ES中热度分
            Optional<Goods> optional = goodsDao.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(Math.round(hotScore));
            goodsDao.save(goods);
        }
    }

    //删除索引  下架索引
    @Override
    public void lowerGoods(Long skuId) {
        goodsDao.deleteById(skuId);
    }


    @Autowired
    private RestHighLevelClient restHighLevelClient;

    //第三步：搜索索引库  入参  返回值
    @Override
    public SearchResponseVo list(SearchParam searchParam) {
        //1:构建搜索条件
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        //2:开始执行搜索
        SearchResponse searchResponse = null;
        try {
            searchResponse =
                    restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //3:解析返回结果
        SearchResponseVo vo = parseSearchResponse(searchResponse);
        //需要一些计算
        //总条数
        //当前页
        vo.setPageNo(searchParam.getPageNo());
        //每页数
        vo.setPageSize(searchParam.getPageSize()); //14条   5 条   3
        //总页数   == (总条数 + 每页数 -1）/每页数  （算法）
        vo.setTotalPages((vo.getTotal() + vo.getPageSize() - 1)/vo.getPageSize());
        return vo;
    }

    //解析返回结果
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();
//        1、分页对象
//                商品集合
//                分页相关
        SearchHits hits = searchResponse.getHits();
        long totalHits = hits.totalHits;
        System.out.println("总条数：" + totalHits);
        vo.setTotal(totalHits);
        SearchHit[] hitsArray = hits.getHits();
        ////////////////////////
        List<Goods> goodsList = Arrays.stream(hitsArray).map(searchHit -> {
            String sourceAsString = searchHit.getSourceAsString();
            //普通名称
            Goods goods = JSONObject.parseObject(sourceAsString, Goods.class);
            //需要高亮的名称
            //判断 有高亮的名称 使用高亮  没有高亮的名称使用普通名称
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            if(null != highlightFields && highlightFields.size() > 0){
                HighlightField highlightField = highlightFields.get("title");
                if(null != highlightField){
                    goods.setTitle(highlightField.fragments()[0].string());
                }
            }
            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);
        //////////////////////////
        //所有聚合获取出来
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
//        2、品牌集合
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> searchResponseTmVoList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo tmVo = new SearchResponseTmVo();
            //品牌ID
            tmVo.setTmId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
            //品牌名称
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            tmVo.setTmName(tmNameAgg.getBuckets().get(0).getKeyAsString());
            //品牌Logo
            ParsedStringTerms tmLogoUrlAgg = bucket.getAggregations().get("tmLogoUrlAgg");
            tmVo.setTmLogoUrl(tmLogoUrlAgg.getBuckets().get(0).getKeyAsString());
            return tmVo;
        }).collect(Collectors.toList());
        vo.setTrademarkList(searchResponseTmVoList);
//        3、平台属性 集合
        ParsedNested attrsAgg = (ParsedNested) aggregationMap.get("attrsAgg");

        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> searchResponseAttrVoList = attrIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //平台属性ID
            attrVo.setAttrId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
            //平台属性名称
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).
                    getAggregations().get("attrNameAgg");
            attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            //平台属性值集合
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).
                    getAggregations().get("attrValueAgg");
            attrVo.setAttrValueList(attrValueAgg.getBuckets().
                    stream().map(Terms.Bucket::getKeyAsString).
                    collect(Collectors.toList()));
            return attrVo;
        }).collect(Collectors.toList());
        vo.setAttrsList(searchResponseAttrVoList);
        return vo;
    }

    //构建搜索条件
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        //组合条件对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //1、关键词  （手机）
        String keyword = searchParam.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            //matchQuery : 模糊查询 （先分词再模糊） OR
            //termQuery : 精准查询
            boolQueryBuilder.must(QueryBuilders.
                    matchQuery("title", keyword).operator(Operator.AND));
        } else {
            //查询所有
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //2、品牌  页面传递过来时候  ID 还是名称  id:名称
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            String[] tm = StringUtils.split(trademark, ":");//缺点： 1:2:3 不能
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", tm[0]));
        }
        //3、平台属性
        String[] props = searchParam.getProps();
        if(null != props && props.length > 0){
            for (String prop : props) {//平台属性ID:属性值名称:属性名称
                String[] p = prop.split(":");
                //判断
                if(p != null && p.length == 3){
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();//中括号
                    // 大括号
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    subBoolQuery.must(QueryBuilders.
                            termQuery("attrs.attrId",p[0]));
                    subBoolQuery.must(QueryBuilders.
                            termQuery("attrs.attrValue",p[1]));
                    //将大括号 设置给 //中括号  嵌套类型
                    boolQuery.must(QueryBuilders.nestedQuery("attrs",subBoolQuery,ScoreMode.None));
                    //将中括号（嵌套对象） 作为过滤条件设置到组合对象中
                    boolQueryBuilder.filter(boolQuery);
                }
            }
        }
        //4、三级分类（一二）
        Long category1Id = searchParam.getCategory1Id();
        if (null != category1Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if (null != category2Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if (null != category3Id) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", category3Id));
        }
        //搜索条件对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(boolQueryBuilder);
        //5、排序相关   order=1:asc
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)){
            String[] o = StringUtils.split(order, ":");
            String s = null;
            switch (o[0]){
                case "1":s="hotScore";break;
                case "2":s="price";break;
            }
            searchSourceBuilder.sort(s,"asc".equals(o[1]) ? SortOrder.ASC:SortOrder.DESC);
        }else{
            //默认 按照综合排序 （热度排序） 降序
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }
        //6、分页  limit 开始行,每页数   limit 0,10   limit 10,10
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        searchSourceBuilder.from((pageNo - 1) * pageSize);
        searchSourceBuilder.size(pageSize);

        //7:高亮   显示的样子 或颜色
        HighlightBuilder highlighter = new HighlightBuilder();
        highlighter.field("title");//需要高亮的域  <font color='red'>手机</font>
        highlighter.preTags("<font color='blue'>");
        highlighter.postTags("</font>");
        searchSourceBuilder.highlighter(highlighter);

        highlighter = searchSourceBuilder.highlighter();
        highlighter.preTags("<font color='red'>");

        //8:根据品牌聚合查询   select max(tmId,tmName,tmLogo) as tmIdAgg  from 表
        // 页面上显示名称及图片
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId")
                        .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                        .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"))
        );
        //9:平台属性及属性值聚合查询
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg","attrs")
                     .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                     .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                     .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue")))
        );
        System.out.println("DSL:" + searchSourceBuilder.toString());

        //构建的搜索请求对象
        SearchRequest searchRequest = new SearchRequest();
        //1:搜索的索引库 的名称
        searchRequest.indices("goods");
        //2:搜索的索引库的表   //ES 7.0版本以上  type:info 已经消失
        //searchRequest.types("info");
        //3:设置条件对象
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }

    public static void main(String[] args) {
        String s = "1:2:3/4";
        String[] split = org.apache.commons.lang3.StringUtils.split(s, ":/");
        //String[] split = StringUtils.split(s,":" );
        System.out.println(split);
    }
}
